// Multiprocessor support
// Search memory for MP description structures.
// http://developer.intel.com/design/pentium/datashts/24201606.pdf

#include "types.h"
#include "defs.h"
#include "constants.h"
#include "mm.h"
#include "mp.h"
#include "x86.h"
#include "proc.h"

extern volatile uint* lapic;

struct cpu cpus[NCPU];
int ncpu;
uchar ioapicid;

uchar sum(uchar *addr, int len) {
	int i, sum;

	sum = 0;
	for (i = 0; i < len; i++)
		sum += addr[i];
	return sum;
}

// Look for an MP structure in the len bytes at addr.
struct mp* mpsearch1(uint a, int len) {
	uchar *e, *p, *addr;

	addr = p2v(a);
	e = addr + len;
	for (p = addr; p < e; p += sizeof(struct mp))
		if (memcmp(p, "_MP_", 4) == 0 && sum(p, sizeof(struct mp)) == 0)
			return (struct mp*) p;
	return 0;
}

// Search for the MP Floating Pointer Structure, which according to the
// spec is in one of the following three locations:
// 1) in the first KB of the EBDA;
// 2) in the last KB of system base memory;
// 3) in the BIOS ROM between 0xE0000 and 0xFFFFF.
struct mp* mpsearch(void) {
	uchar *bda;
	uint p;
	struct mp *mp;

	bda = (uchar *) P2V(0x400);
	if ((p = ((bda[0x0F] << 8) | bda[0x0E]) << 4)) {
		if ((mp = mpsearch1(p, 1024)))
			return mp;
	} else {
		p = ((bda[0x14] << 8) | bda[0x13]) * 1024;
		if ((mp = mpsearch1(p - 1024, 1024)))
			return mp;
	}
	return mpsearch1(0xF0000, 0x10000);
}

// Search for an MP configuration table.  For now,
// don't accept the default configurations (physaddr == 0).
// Check for correct signature, calculate the checksum and,
// if correct, check the version.
// To do: check extended table checksum.
struct mpconf* mpconfig(struct mp **pmp) {
	struct mpconf *conf;
	struct mp *mp;

	if ((mp = mpsearch()) == 0 || mp->physaddr == 0)
		return 0;
	conf = (struct mpconf*) p2v((uint) mp->physaddr);
	if (memcmp(conf, "PCMP", 4) != 0)
		return 0;
	if (conf->version != 1 && conf->version != 4)
		return 0;
	if (sum((uchar*) conf, conf->length) != 0)
		return 0;
	*pmp = mp;
	return conf;
}

void mpinit(void) {
	easyprint("mpinit start -- ");
	uchar *p, *e;
	struct mp *mp;
	struct mpconf *conf;
	struct mpioapic *ioapic;

	if ((conf = mpconfig(&mp)) == 0)
		return;
	lapic = (uint*) conf->lapicaddr;
	for (p = (uchar*) (conf + 1), e = (uchar*) conf + conf->length; p < e;) {
		switch (*p) {
		case MPPROC:
			cpus[ncpu].id = ncpu;
			ncpu++;
			p += sizeof(struct mpproc);
			continue;
		case MPIOAPIC:
			ioapic = (struct mpioapic*) p;
			ioapicid = ioapic->apicno;
			p += sizeof(struct mpioapic);
			continue;
		case MPBUS:
		case MPIOINTR:
		case MPLINTR:
			p += 8;
			continue;
		default:
			easyprint("mpinit: unknown config type");
		}
	}

	if (mp->imcrp) {
		// Bochs doesn't support IMCR, so this doesn't run on Bochs.
		// But it would on real hardware.
		outb(0x22, 0x70);   // Select IMCR
		outb(0x23, inb(0x23) | 1);  // Mask external interrupts.
	}
	easyprint("mpinit end\n");
}
